using System;
using System.Threading;
using System.Threading.Tasks;

/* 任务超时实现方式
 * 1、使用 Task.WhenAny
 * 2、使用 cts 自带的超时自动取消功能
 * 3、利用 Task.WhenAny 封装一个扩展方法 TimeoutAfter
 * 4、使用 ask.WaitAsync
 */
namespace Demo
{
    /// <summary>
    /// 任务超时
    /// </summary>
    public class TaskTimeoutDemo
    {
        /// <summary>
        /// 异步方法，演示任务超时和取消机制
        /// </summary>
        public async void Fun()
        {
            #region 使用带有超时自动取消功能的 CancellationTokenSource
            /*机制: 创建一个带有超时自动取消功能的 CancellationTokenSource。当超时时间到了，ctsOwn.Token 会被触发为取消状态，任务会自动响应取消。
             * 优点:
             * 自动取消: CancellationTokenSource 会在超时时自动触发，不需要手动检测任务是否超时。
             * 适合支持取消的任务: 适用于那些对 CancellationToken 有明确响应的任务，比如 Task.Delay 或执行长时间操作的任务。任务可以在取消时立即停止，从而避免资源浪费。
             * 性能高效: 任务的执行被中断后，不再继续执行，节省了资源。
             * 缺点:
             * 需要任务支持取消: 如果任务不支持 CancellationToken，它不会响应取消信号，超时机制将不起作用。
             * 更适合任务提前停止: 如果只是想监控任务的超时，而不想干预任务的执行，这种方式不太合适，因为它直接取消任务。
             */
            /*var ctsOwn = new CancellationTokenSource(TimeSpan.FromSeconds(3));
            
            // 使用带有超时自动取消功能的 CancellationTokenSource
            await FooAsync(ctsOwn.Token);

            // 释放CancellationTokenSource资源
            ctsOwn.Dispose();*/
            #endregion

            #region 使用 Task.WhenAny 实现超时
            /*// 创建一个可手动触发的CancellationTokenSource
            var cts = new CancellationTokenSource();
            
            // 启动一个任务FooAsync，并传入手动取消的token
            var fooTask = FooAsync(cts.Token);
            
            // 等待FooAsync或Task.Delay(2000)中的任意一个任务完成
            var completedTask = await Task.WhenAny(fooTask, Task.Delay(2000));

            // 如果第一个完成的不是fooTask，说明fooTask超时
            if (completedTask != fooTask)
            {
                // 取消fooTask的执行
                cts.Cancel();
                // 等待fooTask结束
                await fooTask;
                // 打印超时信息
                Console.WriteLine("Timeout...");
            }*/
            #endregion

            

            try
            {
                // 创建一个可手动触发的CancellationTokenSource
                var cts = new CancellationTokenSource();

                // 使用扩展方法 TimeoutAfter，在3秒内执行FooAsync，传入不带取消的token
                await FooAsync(cts.Token).TimeoutAfter(TimeSpan.FromSeconds(3), cts);

                // .NET Framework 4.8 不支持 Task.WaitAsync 方法
                // 因为 WaitAsync 是在 .NET 6 中引入的新方法，用于给异步任务设置超时。
                // await FooAsync(cts.Token).WaitAsync(TimeSpan.FromSeconds(3));

                // 打印成功信息
                Console.WriteLine("Success!");
            }
            catch (TimeoutException)
            {
                // 捕获超时异常，打印超时信息
                Console.WriteLine("Timeout!");
            }
        }

        // 异步任务FooAsync，接收一个CancellationToken，用于取消任务
        async Task FooAsync(CancellationToken token)
        {
            try
            {
                // 打印任务开始
                Console.WriteLine("Foo start...");
                // 模拟耗时操作，等待5秒，支持取消
                await Task.Delay(5000, token);
                // 打印任务结束
                Console.WriteLine("Foo end...");
            }
            catch (OperationCanceledException)
            {
                // 捕获任务被取消的异常，打印取消信息
                Console.WriteLine("Foo cancelled...");
            }
        }
    }

    /// <summary>
    /// 异步扩展方法类
    /// </summary>
    static class AsyncExtensions
    {
        /// <summary>
        /// 扩展方法：带超时和取消的异步任务
        /// </summary>
        /// <param name="task"></param>
        /// <param name="timeout"></param>
        /// <param name="cts"></param>
        /// <exception cref="TimeoutException"></exception>
        public static async Task TimeoutAfter(this Task task, TimeSpan timeout, CancellationTokenSource cts)
        {
            // 创建一个与传入任务并行的延迟任务用于超时控制
            var delayTask = Task.Delay(timeout, cts.Token);

            // 等待传入任务和延迟任务中的任意一个完成
            var completedTask = await Task.WhenAny(task, delayTask);

            // 如果延迟任务先完成，则表示任务超时，触发取消并抛出超时异常
            if (completedTask == delayTask)
            {
                // 取消原始任务的执行
                cts.Cancel();
                throw new TimeoutException();
            }

            // 等待传入任务完成，如果任务已取消或出错，这里会抛出相应的异常
            await task;
        }

        /// <summary>
        /// 重载：支持返回结果的任务
        /// </summary>
        /// <param name="task"></param>
        /// <param name="timeout"></param>
        /// <param name="cts"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        /// <exception cref="TimeoutException"></exception>
        public static async Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout, CancellationTokenSource cts)
        {
            var delayTask = Task.Delay(timeout, cts.Token);

            var completedTask = await Task.WhenAny(task, delayTask);

            if (completedTask == delayTask)
            {
                cts.Cancel();
                throw new TimeoutException();
            }

            return await task;
        }
    }
}